前一天我們練習了HTML Helper的使用方式,並且建立了一個簡單的註冊功能頁面,不過思考一下還有很多地方可以改進,其中之一就是關於資料驗證的部分。譬如表單內的欄位必填、帳號密碼格式是否符合要求、欄位只能填數字...等等,我們必須要確保使用者在填寫表單內容時是照預期地填入,避免後續處理資料麻煩。
表單資料驗證主要可以分為「前端資料驗證」與「後端資料驗證」,前端是指在使用者按下Submit按鈕後,送出表單前就先檢查錯誤並提示;後端則是將資料送至伺服器端驗證進行判斷再回傳錯誤訊息。
前端最簡單的表單驗證方式,就是直接靠HTML5針對<input>標籤提供的屬性功能,例如下列的Code:
<div>
    <form method="post">
        <input type="email" required="required" placeholder="請輸入電子信箱格式" />
        <input type="submit" class="btn btn-primary" />
    </form>
</div>
type="email" 代表必須輸入email格式,否則會提示錯誤。
placeholder屬性可以自訂給使用者填寫的說明;required屬性表示必填,不填送出一樣會提示。
另外也有將欄位設計成特定樣式,像type="number"會多了上下調整數字按鈕,並限制只能輸入數字;type="range"使用滑桿調整數值;type="date"可以直接選取日期清單,有興趣可以自行玩玩,這邊不多做示範。

如果想要讓使用者輸入特定格式內容,例如只能輸入英文大小寫、符合身分證格式等等,則可以使用pattern屬性搭配 正規表達式(Regular Expression)來達成目的。例如下列Code寫法代表欄位必須輸入3個大寫或小寫英文,否則會出現錯誤提示。
<div>
    <form method="post">
        <input type="text" required="required" placeholder="請輸入連續3個英文大小寫" pattern="[a-zA-z]{3}" />
        <br />
        <input type="submit" class="btn btn-primary" />
    </form>
</div>

下列Code寫法則代表欄位必須輸入身分證格式(必須是大寫字母開頭/第2位必須是數字1或2/第3位開始到結尾為8個數字)。
※身分證編碼的更詳細規範可以參考這篇文章:
https://returnbool.pixnet.net/blog/post/10268673
<div>
    <form method="post">
        <input type="text" required="required" placeholder="請輸入身分證字號" pattern="^[A-Z]{1}[1-2]{1}[0-9]{8}$" />
        <br />
        <input type="submit" class="btn btn-primary" />
    </form>
</div>
正規表達式的規則非常多樣化,也可以自由搭配,詳細可以參考維基百科說明:
https://zh.wikipedia.org/zh-tw/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F#PCRE%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%85%A8%E9%9B%86
以上所述的前端驗證方式其實仍有可以改善之處,例如:當不符合正規表達式時可以自訂詳細錯誤內容、錯誤欄位外框可以反紅醒目提示、檢查身分證數字是否符合規則...等等,這些可以利用JavaScript語法技巧來完成,但因為30天主題還是聚焦在ASP .NET MVC的流程,這邊就沒辦法詳細來說明了@_@
後端驗證要講的是關於ASP .NET MVC所提供的模型驗證(Model Validate)方式,我們可以在資料Model的每個屬性上面增加驗證用的屬性,與使用ErrorMessage自訂錯誤訊息。附加的驗證屬性需搭配在Controller的動作方法內,並搭配條件ModelState.IsValid來判斷是否資料有通過驗證。
常用的附加驗證屬性如下:
| 附加屬性名稱 | 用途 | 範例 | 
|---|---|---|
| [DisplayName] | 設定欄位顯示的名稱 | [DisplayName("帳號")] | 
| [Required] | 設定欄位為必填 | [Required] | 
| [Range] | 設定欄位內容數值範圍 | [Range(1, 100, ErrorMessage = "年齡須在1~100之間")] | 
| [Compare] | 與指定的欄位比較內容是否相同 | [Compare("Password", ErrorMessage = "兩組密碼必須相同")] | 
| [EmailAddress] | 驗證欄位是否為Email格式 | [EmailAddress] | 
| [Url] | 驗證欄位是否為網址格式 | [Url] | 
| [StringLength] | 設定欄位字串長度 | [StringLength(12,MinimumLength = 6)] | 
| [RegularExpression] | 設定欄位內容必須符合自訂的正規表達式 | [RegularExpression("[a-zA-z]{3}")] | 
要能使用驗證屬性必須先引用 System.ComponentModel 與 System.ComponentModel.DataAnnotations這兩個命名空間,另外在View畫面會利用@Html.ValidationMessageFor()方法來顯示錯誤訊息,接著用下面範例來說明。
我們利用DAY 8最後註冊頁面的範例來修改一下內容,步驟說明如下:
Member類別的內容如下方Code:    public class Member
    {
        [DisplayName("帳號")]
        [Required(ErrorMessage ="帳號名稱不可空白")]
        public string Id { get; set; }
        [DisplayName("密碼")]
        [Required]
        public string Password { get; set; }
        [DisplayName("再次輸入密碼")]
        [Required]
        [Compare("Password",ErrorMessage ="輸入密碼必須相同")]
        public string Password2 { get; set; }
        [DisplayName("年齡")]
        [Required]
        [Range(0,100,ErrorMessage ="年齡必須在0-100之間")]
        public int Age { get; set; }
        [DisplayName("生日")]
        [Required]
        public DateTime Birthday { get; set; }
        [DisplayName("電子信箱")]
        [Required]
        [EmailAddress(ErrorMessage ="Email格式有誤")]
        public string Email { get; set; }
    }
ValidateController,加入SignUp()動作方法。    public class ValidateController : Controller
    {
        // GET: Validate
        public ActionResult SignUp()
        {
            return View();
        }
    }
Sign()方法的View頁面。@using HTMLHelperDemo.Models
@model Member
@{
    ViewBag.Title = "SignUp";
}
<h2>SignUp</h2>
@using (Html.BeginForm("SignUp", "Validate"))
{
    <div class="container">
        <div class="form-group">
            @Html.LabelFor(m => m.Id)
            @Html.TextBoxFor(m => m.Id, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.Id, "", new { @class = "text-danger" })
        </div>
        <div class="form-group">
            @Html.LabelFor(m => m.Password)
            @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
        </div>
        <div class="form-group">
            @Html.LabelFor(m => m.Password2)
            @Html.PasswordFor(m => m.Password2, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.Password2, "", new { @class = "text-danger" })
        </div>
        <div class="form-group">
            @Html.LabelFor(m => m.Age)
            @Html.TextBoxFor(m => m.Age, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.Age, "", new { @class = "text-danger" })
        </div>
        <div class="form-group">
            @Html.LabelFor(m => m.Email)
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
            @Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })
        </div>
        <div class="form-group">
            @Html.LabelFor(m => m.Birthday)
            @Html.TextBoxFor(m => m.Birthday, new { @class = "form-control", type = "date" })
            @Html.ValidationMessageFor(m => m.Birthday, "", new { @class = "text-danger" })
        </div>
        <div class="form-group">
            <input type="submit" value="註冊" class="btn btn-primary" />
        </div>
    </div>
}
執行畫面如下:
SignUp()的POST方法,當驗證失敗時會回傳原本的頁面與顯示錯誤訊息;成功時則導向到結果呈現頁面,同時利用TempData將表單資料可以帶入指定轉向後的頁面。        [HttpPost]
        public ActionResult SignUp(Member member)
        {
            if (!ModelState.IsValid)
            {
                return View(member);
            }
            else
            {
                TempData["Member"] = member;
                return RedirectToAction("Result");
            }
        }
Result()動作方法,這邊需要指定TempData的型別為Member,才能成功作為View使用的模型資料。
        public ActionResult Result()
        {
            var model = TempData["Member"] as Member;
            return View(model);
        }
DisplayNameFor() 與 DisplayFor()簡單呈現註冊成功後的資料,因為在Member類別內有指定DisplayName Attribute,所以顯示的不再是類別屬性名稱,而是指定的字串了。 
@model HTMLHelperDemo.Models.Member
@{
    ViewBag.Title = "Result";
}
<h2>Result</h2>
<div class="container">
    <ul>
        <li>@Html.DisplayNameFor(m => m.Id) : @Html.DisplayFor(m => m.Id)</li>
        <li>@Html.DisplayNameFor(m => m.Password) : @Html.DisplayFor(m => m.Password)</li>
        <li>@Html.DisplayNameFor(m => m.Password2) : @Html.DisplayFor(m => m.Password2)</li>
        <li>@Html.DisplayNameFor(m => m.Age) : @Html.DisplayFor(m => m.Age)</li>
        <li>@Html.DisplayNameFor(m => m.Email) : @Html.DisplayFor(m => m.Email)</li>
        <li>@Html.DisplayNameFor(m => m.Birthday) : @Html.DisplayFor(m => m.Birthday)</li>
    </ul>
</div>
 

假如View是利用範本建立的話,在form表單上方會看到一行@Html.AntiForgeryToken() 的方法,這個方法主要是用來防止CSRF (Cross Site Request Forgery),中文稱作「跨網站偽造要求」。
或者版友的技術文章:
https://ithelp.ithome.com.tw/articles/10248847
資料驗證的部分就介紹到這邊,接下來要進入另一個重頭戲:資料庫 的操作使用啦~
我們明天見!
※小弟不才,在軟體的世界還只是個小菜雞,如果內容有任何謬誤或問題,還請各位大神前輩們多多批評指教~歡迎下方留言討論^^